package com.hrbxlh.lock;

import com.google.common.collect.Maps;
import com.hrbxlh.constants.LockConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * synchronized+集合实现锁
 * 特点：1.可重入锁，2.锁带过期时间（通过时间戳保证当前只能被加锁的那个线程所解锁）
 *
 * @author lx
 */
@Component
public class KeyLockImpl implements KeyLock<String> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantKeyLockImpl.class);

    /**
     * 存放系统目前已分配的锁，key为锁的key，value为锁的获取时间戳
     */
    private final Map<String, Long> lockPool = new ConcurrentHashMap<>();

    /**
     * 当前线程持有的锁，key为锁的key，value为锁的获取时间戳
     */
    private ThreadLocal<Map<String, Long>> currentThreadHoldLock = ThreadLocal.withInitial(Maps::newHashMap);

    public void lock(String key) {
        while (!addIfNotExists(key)) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    public boolean tryLock(String key) {
        return addIfNotExists(key);
    }

    public boolean tryLock(String key, long timeout, TimeUnit timeUnit) {
        long millTimeout = timeUnit.toMillis(timeout);
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < millTimeout) {
            try {
                if (addIfNotExists(key)) {
                    return true;
                }
                Thread.sleep(millTimeout > 100 ? 100 : millTimeout);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                LOGGER.error(e.getMessage(), e);
            }
        }
        return false;
    }

    public void unlock(String key) {
        remove(key);
    }

    private synchronized boolean addIfNotExists(String key) {
        // 获取当前时间
        long currentTime = System.currentTimeMillis();
        // 如果该锁已被其它线程获取并且锁还未过期，则当前线程获取不到锁
        if (lockPool.containsKey(key)) {
            long latestLockHoldTime = lockPool.get(key);
            if (currentTime - latestLockHoldTime < LockConstant.EXPIRE_TIME) {
                return false;
            }
        }
        // 当前线程获取到锁，记录锁的获取时间
        lockPool.put(key, currentTime);
        currentThreadHoldLock.get().put(key, currentTime);
        return true;
    }

    private synchronized void remove(String key) {
        // 如果当前线程持有该锁，进入释放锁流程
        if (currentThreadHoldLock.get().containsKey(key)) {
            // 查询当前线程获取该锁的时间
            long currentThreadLockHoldTime = currentThreadHoldLock.get().get(key);
            if (lockPool.containsKey(key)) {
                // 查询该锁最新被获取的时间
                long latestLockHoldTime = lockPool.get(key);
                // 如果两个时间一致，说明该锁最后是被当前线程获取的，可以释放锁
                if (currentThreadLockHoldTime == latestLockHoldTime) {
                    lockPool.remove(key);
                }
            }
            // 删除当前线程持有该锁的记录
            currentThreadHoldLock.get().remove(key);
        }
    }
}
